from book import Book
from crawler import Crawler
from bs4 import BeautifulSoup
import requests, json


class DangDang(Crawler):
    '''从当当网查询图书，使用bs4+html.parser'''

    def __init__(self, keyword: str) -> None:
        # 初始化父类
        super().__init__(keyword)

    def search_book(self) -> str:
        # 按图书名称查询图书，爬取搜索结果网页
        # http://search.dangdang.com/?key=python
        # 以字典的形式传递参数
        search_key = {"key": self.book_name}
        # 将访问伪装为浏览器
        headers = {"User-Agent": "Mozilla/5.0"}

        try:
            # 发起访问请求
            resp = requests.get(url="http://search.dangdang.com",
                                params=search_key,
                                headers=headers,
                                timeout=10)

            # 设置字符编码
            resp.encoding = 'gbk'
            return resp.text

        except Exception:
            raise Exception("网络错误")

    def parse_data(self) -> None:
        '''解析网页HTML DOM字符串，从中找出图书信息'''

        # 首先调用类的成员方法search_book()爬取网页
        # 再使用Python自带html解析器解html.parser解析网页
        html_text = BeautifulSoup(self.search_book(), "html.parser")
        
        # 通过HTML标签的属性特征来查找元素/element
        # <ul class="bigimg" id="component_59"></ul>
        # book_doc = html_text.find(id="component_59")
        # bs4的find()方法根据tag名、属性等查找元素
        book_doc = html_text.find(name='ul',
                                  attrs={
                                      'class': ['bigimg'],
                                      'id': 'component_59'
                                  }
        )

        # 每个li标签，就是一本图书的数据
        # bs4的select()方法获得一个element/tag的列表
        for book_tag in book_doc.select('li'):
            # 实例化Book对象
            book = Book()
            # 搜索文档树
            # <p class="name" name="title">该节点图书名称广告太多
            book.title = book_tag.a['title']

            # <p class="search_book_author">
            # 包括作者、出版日期、出版社等3个<span>
            # book_info = book_tag.select('p[class="search_book_author"]')
            book_info = book_tag.find(name='p',
                                      class_='search_book_author').text
            # 形如：曹文 /2022-06-01  /机械工业出版社加入购物车购买电子书收藏
            # 删除掉非图书信息
            book_info = book_info.replace('加入购物车', '') \
                                 .replace('购买电子书', '') \
                                 .replace('收藏', '')
            # 数据有可能不完整
            if len(_book := book_info.split('/')) >= 3:
                # 作者
                book.author = _book[0]
                # 出版社
                book.press = _book[2]

            self.book_data.append(book)

    def save_book(self, file_name: str) -> None:
        '''改写父类方法，以JSON格式保存数据'''
        with open(file_name, mode="w", encoding="utf-8") as fn:
            print("=== 开始写文件 ===")
            for book in self.book_data:
                json.dump(book, fn, ensure_ascii=False, cls=BookEncoder, indent=4)
                fn.write('\n')
            print("=== 完成写文件 ===")


class BookEncoder(json.JSONEncoder):
    """ 扩展JSONEncoder类，以使得Book对象可以
        被序列化

    Args:
        JSONEncoder (class): JSON序列化的基类

    Returns:
        str: JSON string
    """
    def default(self, obj):
        if isinstance(obj, Book):
            return {
                'title': obj.title,
                'author': obj.author,
                'press': obj.press
            }

        # 调用基类的方法抛出异常
        return json.JSONEncoder.default(self, obj)


if __name__ == "__main__":
    spider = DangDang('Python编程基础')

    # 测试 search_book() 方法
    # print(spider.search_book())

    # 测试 parse_data() 方法
    # spider.parse_data()
    # for book in spider.book_data:
    #     print(book)

    # 测试 save_book() 方法
    spider.parse_data()
    spider.save_book('query_book/data/book_db.txt')
